package com.xiashitech.apiservice.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.xiashitech.apiservice.Constants.ApiConstant;
import com.xiashitech.apiservice.Constants.ErrorCode;
import com.xiashitech.apiservice.config.AuthConfig;
import com.xiashitech.apiservice.dto.Api;
import com.xiashitech.apiservice.dto.ApiService;
import com.xiashitech.apiservice.dto.Param;
import com.xiashitech.apiservice.dto.System;
import com.xiashitech.apiservice.service.IApiMetadataService;
import com.xiashitech.interfaces.agent.constants.XSConstants;
import com.xiashitech.interfaces.agent.dto.api.ApiRequest;
import com.xiashitech.interfaces.agent.dto.api.ApiResponse;
import com.xiashitech.interfaces.agent.dto.api.Params;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor(onConstructor = @_(@Autowired))
public class ApiMetadataServiceImpl implements IApiMetadataService {
    private static final Log log = LogFactory.get();
    private final AuthConfig authConfig;

    private static final String SEPARATOR = ".";

    private static final String ERROR_RESULT = "404";

    private static final String APISERVICE_TYPE = "apiService";

    private static final String SYSTEM = "system";

    private static final String LIMIT = "limit";

    /**
     * 将agent捕获的全量api传输给openmetadata中进行保存，保存逻辑为：
     * 1、先查询apiService是否存在，不存在就创建，默认只有一个apiService实例
     * 2、查询systemId是否存在，不存在就创建
     * 3、查询所有api实例，判断是否已经存在，不存在的就创建
     *
     * @param apiRequest
     * @return
     */
    @Override
    public ApiResponse syncApisToOpenMetadata(ApiRequest apiRequest) {
        /**
         * 获取apiService，没有则创建
         */
        List<ApiService> apiServices = getApiServices();
        ApiService apiService;
        if (apiServices != null && apiServices.size() > 0) {
            apiService = apiServices.get(0);
        } else {
            apiService = createApiService(ApiService.builder().name(ApiConstant.APISERVICE_NAME).displayName(ApiConstant.APISERVICE_NAME).serviceType(ApiConstant.APISERVICE_SERVICETYPE).build());
        }
        if (apiService == null) {
            return ApiResponse.builder().code(ErrorCode.RESULT_4012.getCode()).message(ErrorCode.RESULT_4012.getMessage()).build();
        }
        /**
         * 获取system，没有则创建
         */
        System system = getSystemByFQN(apiService.getName() + SEPARATOR + apiRequest.getSystemId());
        if (system == null) {
            system = createSystem(System.builder().name(apiRequest.getSystemId()).displayName(apiRequest.getSystemId()).service(ApiService.builder().id(apiService.getId()).type(APISERVICE_TYPE).build()).build());
        }
        if (system == null) {
            return ApiResponse.builder().code(ErrorCode.RESULT_4013.getCode()).message(ErrorCode.RESULT_4013.getMessage()).build();
        }
        /**
         * 查询系统下的全量api，若agent捕获的api不在全量api里则将捕获的api保存到openmetadata中
         */
        String openmetadataApiServiceId = apiService.getId();
        String openmetadataSystemId = system.getId();
        List<Api> apisNewCreated = apiRequest.getApis().stream()
                .flatMap(api -> api.getPaths().stream()
                        .map(path -> Api.builder()
                                .name(path.getName().replace("/", "_"))
                                .path(path.getName())
                                .httpMethod(api.getHttpMethods().stream().map(httpMethod -> httpMethod.getName()).collect(Collectors.toList()))
                                .displayName(path.getName())
                                .apiType(StrUtil.isEmpty(api.getApiType()) ? XSConstants.ApiType.RESTFUL : api.getApiType())
                                .requestParams(api.getRequestParams().stream()
                                        .map(params -> Param.builder()
                                                .name(params.getName())
                                                .displayName(params.getName()) //.split("/")[params.getName().split("/").length - 1])
                                                .dataType(params.getType())
                                                .dataTypeDisplay(params.getType())
                                                .subParams(getSubParams(params.getSubParams()))
                                                .build())
                                        .collect(Collectors.toList()))
                                .responseParams(api.getResponseParams().stream()
                                        .map(params -> Param.builder()
                                                .name(params.getName())
                                                .displayName(params.getName().split("/")[params.getName().split("/").length - 1])
                                                .dataType(params.getType())
                                                .dataTypeDisplay(params.getType())
                                                .subParams(getSubParams(params.getSubParams()))
                                                .build())
                                        .collect(Collectors.toList()))
                                .system(System.builder().id(openmetadataSystemId).type(SYSTEM).build())
                                .service(ApiService.builder().id(openmetadataApiServiceId).type(APISERVICE_TYPE).build())
                                .build()))
                .collect(Collectors.toList());
        List<Api> apis = getAllApiBySystem(apiService.getName() + SEPARATOR + apiRequest.getSystemId());
        Map<String, Api> apiMap = apis.stream().collect(Collectors.toMap(Api::getPath, Function.identity()));
        apisNewCreated = apisNewCreated.stream().filter(api -> !apiMap.containsKey(api.getPath())).collect(Collectors.toList());
        createApis(apisNewCreated);
        return ApiResponse.builder().code(ErrorCode.RESULT_1001.getCode()).message(ErrorCode.RESULT_1001.getMessage()).build();
    }

    /**
     * 拦截Request请求动态补全 API 出入参
     *
     * @param apiRequest
     * @return
     */
    @Override
    public ApiResponse syncApiParamsToOpenMetadata(ApiRequest apiRequest) {
        //if (apiRequest != null && CollUtil.isNotEmpty(apiRequest.getApis()) && StrUtil.isNotEmpty(apiRequest.getUpdateParamType())){
        if (apiRequest != null && CollUtil.isNotEmpty(apiRequest.getApis())) {
            Optional<ApiService> apiServiceOptional = getApiServices().stream().findFirst();
            if (!apiServiceOptional.isPresent()) {
                return ApiResponse.builder().code(ErrorCode.RESULT_4001.getCode()).message(ErrorCode.RESULT_4001.getMessage()).build();
            }
            ApiService apiService = apiServiceOptional.get();
            System system = getSystemByFQN(apiService.getName() + SEPARATOR + apiRequest.getSystemId());
            if (Objects.isNull(system)) {
                return ApiResponse.builder().code(ErrorCode.RESULT_4001.getCode()).message(ErrorCode.RESULT_4001.getMessage()).build();
            }
            String openmetadataApiServiceId = apiService.getId();
            String openmetadataSystemId = system.getId();
            // 转换API对象
            List<Api> apisNew = apiRequest.getApis().stream().flatMap(api -> api.getPaths().stream().map(path ->
                    Api.builder().name(path.getName().replace("/", "_"))
                            .path(path.getName())
                            .httpMethod(api.getHttpMethods().stream().map(httpMethod -> httpMethod.getName()).collect(Collectors.toList()))
                            .displayName(path.getName())
                            .apiType(StrUtil.isEmpty(api.getApiType()) ? XSConstants.ApiType.RESTFUL : api.getApiType())
                            .requestParams(api.getRequestParams().stream().map(params -> Param.builder().name(params.getName())
                                    .displayName(params.getName()) //.split("/")[params.getName().split("/").length - 1])
                                    .dataType(params.getType())
                                    .dataTypeDisplay(params.getType())
                                    .subParams(getSubParams(params.getSubParams()))
                                    .build()).collect(Collectors.toList()))
                            .responseParams(api.getResponseParams().stream().map(params -> Param.builder().name(params.getName())
                                    .displayName(params.getName().split("/")[params.getName().split("/").length - 1])
                                    .dataType(params.getType())
                                    .dataTypeDisplay(params.getType())
                                    .subParams(getSubParams(params.getSubParams()))
                                    .build()).collect(Collectors.toList()))
                            .system(System.builder().id(openmetadataSystemId).type(SYSTEM).build())
                            .service(ApiService.builder().id(openmetadataApiServiceId).type(APISERVICE_TYPE).build())
                            .build())).collect(Collectors.toList());
            createApis(apisNew);
            //updateApiParams(apisNew, apiRequest.getUpdateParamType());
        }
        return ApiResponse.builder().code(ErrorCode.RESULT_1001.getCode()).message(ErrorCode.RESULT_1001.getMessage()).build();
    }

    private void updateApiParams(List<Api> apis, String updateType) {
        String url = StrUtil.format(authConfig.getApiUrlForUpdateApiParam(), updateType);
        try {
            apis.forEach(api -> {
                HttpResponse httpResponse = HttpRequest.put(url)
                        .body(JSONUtil.toJsonStr(api), "application/json")
                        .timeout(5 * 1000)
                        .execute();
                if (!(httpResponse.isOk() && JSONUtil.isTypeJSON(httpResponse.body()))) {
                    log.error("the api->{} params is not updated to the openmetadata", api.getPath());
                    log.error(httpResponse.toString());
                }
            });
        } catch (Exception e) {
            log.error("call {} failure.", url);
            log.error(e);
        }
    }

    /**
     * 将agent捕获的返回值里的key同步到openmetadata的desenseParam参数
     *
     * @param apiRequest
     * @return
     */
    public ApiResponse generateDesenseParam(ApiRequest apiRequest) {
        List<ApiService> apiServices = getApiServices();
        ApiService apiService;
        if (apiServices != null && apiServices.size() > 0) {
            apiService = apiServices.get(0);
        } else {
            return ApiResponse.builder().code(ErrorCode.RESULT_4017.getCode()).message(ErrorCode.RESULT_4017.getMessage()).build();
        }
        String fqn = apiService.getName() + ApiConstant.FQN_SEPARATOR + apiRequest.getDesenseParam().getSystem() + ApiConstant.FQN_SEPARATOR + apiRequest.getDesenseParam().getApiName();
        StringJoiner stringJoiner = new StringJoiner(",");
        apiRequest.getDesenseParam().getDesenseParam().stream().forEach(stringJoiner::add);
        String desenseParam = stringJoiner.toString();
        if (updateDesenseParam(fqn, desenseParam)) {
            return ApiResponse.builder().code(ErrorCode.RESULT_1001.getCode()).message(ErrorCode.RESULT_1001.getMessage()).build();
        } else {
            return ApiResponse.builder().code(ErrorCode.RESULT_4001.getCode()).message(ErrorCode.RESULT_4001.getMessage()).build();
        }
    }

    private List<Param> getSubParams(List<Params> subParams) {
        if (subParams == null || subParams.size() == 0) {
            return null;
        }
        return subParams.stream().map(param -> Param.builder().name(param.getName()).displayName(param.getName()).dataType(param.getType()).dataTypeDisplay(param.getType()).build()).collect(Collectors.toList());
    }

    private void createApis(List<Api> apis) {
        try {
            apis.forEach(api -> {
                createApi(api);
            });
        } catch (Exception e) {
            log.error(e);
        }
    }

    private void createApi(Api api) {
        try {
            HttpResponse httpResponse = HttpRequest.put(authConfig.getApiUrlForCreateOrUpdateApi())
                    .body(JSONUtil.toJsonStr(api), "application/json")
                    .timeout(30 * 1000)
                    .execute();
            if (!(httpResponse.isOk() && JSONUtil.isTypeJSON(httpResponse.body()))) {
                log.error("the api->{} is not saved to the openmetadata", api.getPath());
                log.error(httpResponse.toString());
            }
        } catch (Exception e) {
            log.error(e);
        }
    }

    private boolean updateDesenseParam(String fqn, String desenseParam) {
        boolean success = false;
        try {
            HttpResponse httpResponse = HttpRequest.put(StrUtil.format(authConfig.getApiUrlForUpdateDesenseParam(), fqn, desenseParam))
                    .contentType("application/json")
                    .timeout(30 * 1000)
                    .execute();
            if (!(httpResponse.isOk() && JSONUtil.isTypeJSON(httpResponse.body()))) {
                log.error("the desenseParam{} is not updated for the api{}", desenseParam, fqn);
            } else {
                success = true;
            }
        } catch (Exception e) {
            log.error(e);
        }
        return success;
    }

    private List<Api> getAllApiBySystem(String sysFQN) {
        try {
            String apiJsons = HttpUtil.createGet(authConfig.getApiUrlForListApis()).contentType("application/json").form(SYSTEM, sysFQN).form(LIMIT, 10000).execute().body();
            JSONObject jsonObject = JSONUtil.parseObj(apiJsons);
            return JSONUtil.toList(jsonObject.get("data").toString(), Api.class);
        } catch (Exception e) {
            log.error(e);
        }
        return new ArrayList();
    }

    private System getSystemByFQN(String fqn) {
        try {
            String getSystemByFQN = StrUtil.format(authConfig.getApiUrlForGetSystemByFQN(), fqn);
            String systemJson = HttpUtil.createGet(getSystemByFQN).contentType("application/json").execute().body();
            System system = JSONUtil.toBean(systemJson, System.class);
            if (ERROR_RESULT.equals(system.getCode())) {
                return null;
            } else {
                return system;
            }
        } catch (Exception e) {
            log.error(e);
        }
        return null;
    }

    private System createSystem(System system) {
        try {
            HttpResponse httpResponse = HttpRequest.put(authConfig.getApiUrlForCreateOrUpdateSystem())
                    .body(JSONUtil.toJsonStr(system), "application/json")
                    .timeout(30 * 1000)
                    .execute();
            if (httpResponse.isOk() && JSONUtil.isTypeJSON(httpResponse.body())) {
                return JSONUtil.toBean(httpResponse.body(), System.class);
            } else {
                return null;
            }
        } catch (Exception e) {
            log.error(e);
        }
        return null;
    }

    private List<ApiService> getApiServices() {
        try {
            String apiServiceJsons = HttpUtil.createGet(authConfig.getApiUrlForListApiServices()).contentType("application/json").execute().body();
            JSONObject jsonObject = JSONUtil.parseObj(apiServiceJsons);
            return JSONUtil.toList(jsonObject.get("data").toString(), ApiService.class);
        } catch (Exception e) {
            log.error(e);
        }
        return new ArrayList();
    }

    private ApiService createApiService(ApiService apiService) {
        try {
            HttpResponse httpResponse = HttpRequest.put(authConfig.getApiUrlForCreateOrUpdateApiService())
                    .body(JSONUtil.toJsonStr(apiService), "application/json")
                    .timeout(30 * 1000)
                    .execute();
            if (httpResponse.isOk() && JSONUtil.isTypeJSON(httpResponse.body())) {
                ApiService apiServiceForResponse = JSONUtil.toBean(httpResponse.body(), ApiService.class);
                return apiServiceForResponse;
            } else {
                return null;
            }
        } catch (Exception e) {
            log.error(e);
        }
        return null;
    }

    public JSONObject getApiByFQN(String fqn) {
        try {
            String metaUrl = StrUtil.format(authConfig.getApiUrlForGetApiByFQN(), fqn);
            String resultJson = HttpRequest.get(metaUrl).timeout(30 * 1000).execute().body();
            return JSONUtil.parseObj(resultJson);
            //ApiService apiServiceForResponse = JSONUtil.toBean(resultJson, ApiService.class);
        } catch (Exception e) {
            log.error(e);
        }
        return null;
    }

}
